define(`for_incr',`ifelse($#,0,``$0'',`ifelse(eval($2<=$3),1,
    `pushdef(`$1',$2)$4`'popdef(`$1')$0(`$1',incr($2),$3,`$4')')')')dnl
define(`for_decr',`ifelse($#,0,``$0'',`ifelse(eval($2>=$3),1,
    `pushdef(`$1',$2)$4`'popdef(`$1')$0(`$1',decr($2),$3,`$4')')')')dnl
define(`idx', `i$1')
define(`reshape_array',
  `ibuf = buffer%is
for_decr(`dim', $1, 1, `do idx(dim) = 1, size(array, dim)
')dnl
array(for_incr(`dim', 1, $1, `idx(dim) ifelse(dim,$1,``'',``,'')')) = buffer%$2(ibuf)
ibuf = ibuf + 1
for_incr(`dim', 1, $1, `end do
')')

define(`fiona_input_0d', `
  subroutine fiona_input_0d_$1(dataset_name, var_name, value, start, time_step, fatal)

    character(*), intent(in) :: dataset_name
    character(*), intent(in) :: var_name
    $2, intent(out) :: value
    integer, intent(in), optional :: start(:)
    integer, intent(in), optional :: time_step
    logical, intent(in), optional :: fatal

    type(dataset_type), pointer :: dataset
    integer, allocatable :: start_opt(:)
    integer ierr, varid

    dataset => get_dataset(dataset_name)

    if (present(time_step)) then
      if (present(start)) then
        allocate(start_opt(size(start)+1)); start_opt(1:size(start)) = start; start_opt(size(start)+1) = time_step
      else
        allocate(start_opt(1)); start_opt(1) = time_step
      end if
    else
      if (present(start)) then
        allocate(start_opt(size(start))); start_opt = start
      else
        allocate(start_opt(1)); start_opt(1) = 1
      end if
    end if

    if (dataset%is_group_root) then
      ierr = NF90_INQ_VARID(dataset%id, var_name, varid)
      call handle_error(ierr, "No variable """ // trim(var_name) // """ in dataset """ // &
                        trim(dataset%file_path) // """!", __FILE__, __LINE__)
      ierr = NF90_GET_VAR(dataset%id, varid, value, start=start_opt)
      call handle_error(ierr, "Failed to read variable """ // trim(var_name) // """ in dataset """ // &
                        trim(dataset%file_path) // """!", __FILE__, __LINE__)
    end if
#ifdef HAS_MPI
    if (dataset%group_mpi_comm /= MPI_COMM_NULL) then
      call MPI_BCAST(value, 1, $3, 0, dataset%group_mpi_comm, ierr)
    end if
#endif

    if (allocated(start_opt)) deallocate(start_opt)

  end subroutine fiona_input_0d_$1')

define(`fiona_input', `
  subroutine fiona_input_$1d_$4(dataset_name, var_name, array, start, count, time_step, fatal)

    integer, parameter :: n = $1

    character(*), intent(in) :: dataset_name
    character(*), intent(in) :: var_name
    $5, intent(out) :: array$2
    integer, intent(in), optional :: start(n)
    integer, intent(in), optional :: count(n)
    integer, intent(in), optional :: time_step
    logical, intent(in), optional :: fatal

    type(dataset_type), pointer :: dataset
    type(var_type), pointer :: var
    type(buffer_type) buffer
    real(8) add_offset, scale_factor
    logical fatal_opt, use_add_offset_and_scale_factor
    integer i, ierr, varid
    integer ibuf, for_incr(`dim', 1, $1, `idx(dim) ifelse(dim,$1,``'',``,'')')

    ! All count should be greater than zero!
    if (present(count)) then
      if (any(count <= 0)) then
        call log_error("Variable " // trim(var_name) // " dimension is problematic!")
      end if
    end if

    fatal_opt = .false.; if (present(fatal)) fatal_opt = fatal

    dataset => get_dataset(dataset_name)
    var => dataset%get_var(var_name)
    call gather_start_count(dataset, n, shape(array), start, count)
    if (dataset%is_group_root) then
      ierr = NF90_INQ_VARID(dataset%id, var_name, varid)
      call handle_error(ierr, "No variable """ // trim(var_name) // """ in dataset """ // &
                        trim(dataset%file_path) // """!", __FILE__, __LINE__)
      ierr = NF90_GET_ATT(dataset%id, varid, "add_offset", add_offset)
      use_add_offset_and_scale_factor = ierr == NF90_NOERR
      ierr = NF90_GET_ATT(dataset%id, varid, "scale_factor", scale_factor)
      use_add_offset_and_scale_factor = ierr == NF90_NOERR
      call buffer%init(buffer_size(dataset, n), array$3)
#ifdef HAS_MPI
      if (dataset%is_parallel) then
        ierr = NF90_VAR_PAR_ACCESS(dataset%id, varid, NF90_COLLECTIVE)
        call handle_error(ierr, "Failed to set parallel access for variable " // trim(var_name) // "!", __FILE__, __LINE__)
      end if
#endif
    end if
    do i = 1, dataset%group_nproc
      if (dataset%is_group_root .or. dataset%group_proc_id == i - 1) then
        if (dataset%is_group_root) then
          if (product(dataset%group_count(1:n,i)) > 0) then
            call buffer%shift_index(product(dataset%group_count(1:n,i)))
            if (present(time_step)) then
              ierr = NF90_GET_VAR(dataset%id, varid, buffer%$4(buffer%is:buffer%ie), &
                start=[dataset%group_start(1:n,i),time_step], &
                count=[dataset%group_count(1:n,i),1])
            else
              ierr = NF90_GET_VAR(dataset%id, varid, buffer%$4(buffer%is:buffer%ie), &
                start=dataset%group_start(1:n,i), &
                count=dataset%group_count(1:n,i))
            end if
            if (use_add_offset_and_scale_factor) then
              buffer%$4(buffer%is:buffer%ie) = scale_factor * buffer%$4(buffer%is:buffer%ie) + add_offset
            end if
          end if
          if (i == 1) then
            if (buffer%is > 0) then
            reshape_array($1, `$4')
            end if
          else
#ifdef HAS_MPI
            call MPI_ISEND(buffer%$4(buffer%is:buffer%ie), buffer%ie-buffer%is+1, $6, i-1, 99, &
                           dataset%group_mpi_comm, dataset%group_send_req(i), ierr)
#endif
          end if
        else
#ifdef HAS_MPI
          call MPI_RECV(array, size(array), $6, 0, 99, dataset%group_mpi_comm, MPI_STATUS_IGNORE, ierr)
#endif
        end if
        if (fatal_opt) then
          call handle_error(ierr, "Failed to read variable """ // trim(var_name) // """ in dataset """ // &
                            trim(dataset%file_path) // """!", __FILE__, __LINE__)
        end if
      end if
    end do
    call dataset%wait_send()
    call buffer%clear()

  end subroutine fiona_input_$1d_$4')

fiona_input_0d(`i2', `integer(2)', `MPI_SHORT')
fiona_input_0d(`i4', `integer(4)', `MPI_INT')
fiona_input_0d(`r4', `real(4)', `MPI_FLOAT')
fiona_input_0d(`r8', `real(8)', `MPI_DOUBLE')
fiona_input(`1', `(:)', `(1)', `i2', `integer(2)', `MPI_SHORT')
fiona_input(`1', `(:)', `(1)', `i4', `integer(4)', `MPI_INT')
fiona_input(`1', `(:)', `(1)', `r4', `real(4)', `MPI_FLOAT')
fiona_input(`1', `(:)', `(1)', `r8', `real(8)', `MPI_DOUBLE')
fiona_input(`2', `(:,:)', `(1,1)', `i2', `integer(2)', `MPI_SHORT')
fiona_input(`2', `(:,:)', `(1,1)', `i4', `integer(4)', `MPI_INT')
fiona_input(`2', `(:,:)', `(1,1)', `r4', `real(4)', `MPI_FLOAT')
fiona_input(`2', `(:,:)', `(1,1)', `r8', `real(8)', `MPI_DOUBLE')
fiona_input(`3', `(:,:,:)', `(1,1,1)', `i2', `integer(2)', `MPI_SHORT')
fiona_input(`3', `(:,:,:)', `(1,1,1)', `i4', `integer(4)', `MPI_INT')
fiona_input(`3', `(:,:,:)', `(1,1,1)', `r4', `real(4)', `MPI_FLOAT')
fiona_input(`3', `(:,:,:)', `(1,1,1)', `r8', `real(8)', `MPI_DOUBLE')
fiona_input(`4', `(:,:,:,:)', `(1,1,1,1)', `i2', `integer(2)', `MPI_SHORT')
fiona_input(`4', `(:,:,:,:)', `(1,1,1,1)', `i4', `integer(4)', `MPI_INT')
fiona_input(`4', `(:,:,:,:)', `(1,1,1,1)', `r4', `real(4)', `MPI_FLOAT')
fiona_input(`4', `(:,:,:,:)', `(1,1,1,1)', `r8', `real(8)', `MPI_DOUBLE')
fiona_input(`5', `(:,:,:,:,:)', `(1,1,1,1,1)', `i2', `integer(2)', `MPI_SHORT')
fiona_input(`5', `(:,:,:,:,:)', `(1,1,1,1,1)', `i4', `integer(4)', `MPI_INT')
fiona_input(`5', `(:,:,:,:,:)', `(1,1,1,1,1)', `r4', `real(4)', `MPI_FLOAT')
fiona_input(`5', `(:,:,:,:,:)', `(1,1,1,1,1)', `r8', `real(8)', `MPI_DOUBLE')
fiona_input(`6', `(:,:,:,:,:,:)', `(1,1,1,1,1,1)', `i2', `integer(2)', `MPI_SHORT')
fiona_input(`6', `(:,:,:,:,:,:)', `(1,1,1,1,1,1)', `i4', `integer(4)', `MPI_INT')
fiona_input(`6', `(:,:,:,:,:,:)', `(1,1,1,1,1,1)', `r4', `real(4)', `MPI_FLOAT')
fiona_input(`6', `(:,:,:,:,:,:)', `(1,1,1,1,1,1)', `r8', `real(8)', `MPI_DOUBLE')
fiona_input(`7', `(:,:,:,:,:,:,:)', `(1,1,1,1,1,1,1)', `i2', `integer(2)', `MPI_SHORT')
fiona_input(`7', `(:,:,:,:,:,:,:)', `(1,1,1,1,1,1,1)', `i4', `integer(4)', `MPI_INT')
fiona_input(`7', `(:,:,:,:,:,:,:)', `(1,1,1,1,1,1,1)', `r4', `real(4)', `MPI_FLOAT')
fiona_input(`7', `(:,:,:,:,:,:,:)', `(1,1,1,1,1,1,1)', `r8', `real(8)', `MPI_DOUBLE')
